package engine.classes.entity
{
	import ash.core.Entity;
	
	import engine.groups.DisplayGroup;
	import engine.utils.ClassUtils;
	import engine.utils.DataUtils;
	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	
	public class EntityParser
	{
		public var debug:Boolean;
		public var group:DisplayGroup;
		public var classParsers:Dictionary;
		
		public function EntityParser(group:DisplayGroup, classParsers:Dictionary = null, debug:Boolean = false)
		{
			this.group 			= group;
			this.classParsers 	= classParsers;
			this.debug 			= debug;
		}
		
		/**
		 * EntityParser.parse() dynamically creates entities from XML. An <code>entities.xml</code> is divided into
		 * 4 core sections: <b>entities</b>, <b>components</b>, <b>constructor</b>, and <b>properties</b>.
		 * 
		 * <p>The <b>entities</b> section lists each Entity, and each Entity has a <code>name</code> attribute.
		 * The Group attempts to find an existing Entity with that <code>name</code> (ex. "entity123"). If none is found,
		 * it creates a new Entity with that <code>name</code> and adds it to the Group.
		 * 
		 * <p>The <b>components</b> section lists each Component, and each Component has a <code>className</code> attribute.
		 * This <code>className</code> <i>must</i> match an existing Class definition (ex. "engine.components.Spatial").
		 * 
		 * <p>The <b>constructor</b> sections list each variable that needs to go to an Object's constructor <i><b>in the
		 * order the Object's constructor takes them</b></i>. Constructor variables can be named anything in XML, but for
		 * clarity they <i>should</i> be named after the Object's property they're being assigned to.
		 * 
		 * <p>The <b>properties</b> sections list each variable of an Object that needs to be assigned a value <i>after</i>
		 * the Object has been constructed. Property variables <i><b>must be named after the Object variable they're
		 * being assigned to</b></i>. For Arrays, property variables may be numbers to correspond to an index.
		 * 
		 * <p>Not all Objects can be constructed straight from XML. Some might require communication with the Group
		 * (ex. for finding DisplayObjects). In these cases, you can use a Dictionary of Class handler Functions. Each
		 * handler should take an XMLList of properties
		 * 
		 * <p>Not all Class constructors have primitive data types. In the case of complex data types (ex. DisplayObjects),
		 * You can pass a dictionary of Class handler Functions. Each handler should take an XMLList of properties and an
		 * EntityBuilder (ex. this). This allows the handler to communicate with the EntityBuilder, which also has a Group
		 * reference should more information or access be needed.
		 */
		public function parse(xml:XML):void
		{
			//Gets the XMLList of entities.
			var entitiesXML:XMLList = xml.children();
			for each(var entityXML:XML in entitiesXML)
			{
				//What the entity will be named.
				var name:String = entityXML.attribute("name");
				
				//Attempts to find an existing Entity. If none is found, it makes one and adds it to the Group.
				var entity:Entity = this.group.getEntityByName(name);
				if(!entity)
				{
					entity = new Entity(name);
					this.group.addEntity(entity);
				}
				
				//Gets the XMLList of entity components.
				var componentsXML:XMLList = entityXML.children();
				for each(var componentXML:XML in componentsXML)
				{
					//Each Component must have a className to construct an instance from.
					var componentClass:Class = Class(getDefinitionByName(componentXML.attribute("className")));
					
					//Attempts to get an existing Component from the Entity.
					var component:* = entity.get(componentClass);
					var add:Boolean = false;
					
					if(!component)
					{
						add = true;
						component = this.parseConstructorXML(componentXML, componentClass);
					}
					
					//Parses the XMLList of Component property variables.
					this.parsePropertiesXML(componentXML, componentClass, component);
					
					if(add) entity.add(component);
				}
			}
		}
		
		private function parseConstructorXML(classXML:XML, objectClass:Class):*
		{
			var object:*;
			
			if(classXML.hasOwnProperty("constructor"))
			{
				var constructorXML:XMLList = classXML.constructor.children();
				if(constructorXML.length() > 0)
				{
					var properties:Array = this.getClassValues(constructorXML, objectClass);
					object = ClassUtils.construct(objectClass, properties);
				}
			}
			
			if(!object) object = new objectClass();
			
			return object;
		}
		
		private function parsePropertiesXML(classXML:XML, objectClass:Class, object:*):void
		{
			if(classXML.hasOwnProperty("properties"))
			{
				var propertiesXML:XMLList = classXML.properties.children();
				var length:int = propertiesXML.length();
				if(length > 0)
				{
					var properties:Array = this.getClassValues(propertiesXML, objectClass);
					for(var i:int = 0; i < length; i++)
					{
						var propertyXML:XML = propertiesXML[i];
						if(propertyXML.attribute("className").toString() == "Function")
						{
							object[propertyXML.name()].apply(null, properties[i]);
						}
						else
						{
							object[DataUtils.cast(propertyXML.name())] = properties[i];
						}
					}
				}
			}
		}
		
		private function getClassValues(propertiesXML:XMLList, objectClass:Class = null):Array
		{
			var properties:Array = [];
			
			if(this.classParsers)
			{
				var handler:Function = this.classParsers[objectClass];
				
				if(handler != null) handler(propertiesXML, properties, this);
				else this.setClassValues(propertiesXML, properties);
			}
			else this.setClassValues(propertiesXML, properties);
			
			return properties;
		}
		
		public function setClassValues(propertiesXML:XMLList, properties:Array):void
		{
			for each(var propertyXML:XML in propertiesXML)
			{
				var className:String = propertyXML.attribute("className").toString();
				
				if(className.length > 0)
				{
					if(className == "Function")
					{
						properties.push(this.getClassValues(propertyXML.children()));
					}
					else
					{
						var objectClass:Class = Class(getDefinitionByName(className));
						var object:* = this.parseConstructorXML(propertyXML, objectClass);
						
						this.parsePropertiesXML(propertyXML, objectClass, object);
						
						properties.push(object);
					}
				}
				else
				{
					properties.push(DataUtils.cast(propertyXML));
				}
			}
		}
		
		public function parseRender(classXML:XMLList, properties:Array, parser:EntityParser):void
		{
			var propertyXML:XML = classXML[0];
			
			var names:Array = String(propertyXML).split(".");
			var display:DisplayObject = parser.group.groupContainer.parent;	
			for each(var name:String in names)
			{
				if(name == "parent") display = display.parent;
				else if(name == "stage") display = display.stage;
				else display = DisplayObjectContainer(display).getChildByName(name);
			}
			
			properties.push(display);
			
			if(parser.debug) trace("Property:", propertyXML.name(), propertyXML);
		}
	}
}